using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

using System.Runtime.InteropServices;

namespace BardsTaleViewer {
  public class SpriteReader : IDisposable {
    private static uint _keyedARGB = 0xFFFF00FF;
    private static uint _transparentARGB = 0x00FF00FF;
    public static byte _keyIndexColor = 0;

    private Bitmap _bitmap;
    private int _width;
    private int _height;

    public enum ModesEnum {
      Chunky,
      Interleaved,
      Scanline,
      Planar,
    }

    public enum MasksEnum {
      Opaque,
      Transparent,
      Keyed
    }

    public SpriteReader (byte [] bytes, int offset, int width, int height, int bp, ModesEnum mode, int interleave, Palette palette, int paletteOffset, MasksEnum mask) {
      _width = width;
      _height = height;
      _bitmap = CreateBitmap (bytes, offset, width, height, bp, mode, interleave, palette, paletteOffset, mask);
    }

    public static Bitmap [] CreateBitmaps (byte [] bytes, int offset, int nb, int width, int height, int bp, ModesEnum mode, int interleave, Palette palette, int paletteOffset, MasksEnum mask) {
      Bitmap [] bitmaps = new Bitmap [nb];
      int size = CalcSizeInBytes (width, height, bp, mode);
      for (int i = 0; i < nb; i++) {
        bitmaps [i] = CreateBitmap (bytes, offset + size * i, width, height, bp, mode, interleave, palette, paletteOffset, mask);
      }
      return (bitmaps);
    }

    public static Bitmap CreateBitmap (byte [] bytes, int offset, int width, int height, int bp, ModesEnum mode, int interleave, Palette palette, int paletteOffset, MasksEnum mask) {
      Rectangle rectangle = new Rectangle (0, 0, width, height);
      return (CreateBitmap (bytes, offset, rectangle, width, height, bp, mode, interleave, palette, paletteOffset, mask));
    }

    public static Bitmap CreateBitmap (byte [] bytes, int offset, Rectangle rectangle, int width, int height, int bp, ModesEnum mode, int interleave, Palette palette, int paletteOffset, MasksEnum mask) {
      Bitmap bitmap = null;
      if (mode == ModesEnum.Chunky) {
        if (bp == 4) {
          bitmap = ReadChunky_4bp (bytes, offset, width, height, palette, paletteOffset, mask);
        }
        else {
          throw new Exception ();
        }
      }
      else if (mode == ModesEnum.Interleaved) {
        int interleaveSize = interleave;
        int scanlineStride = width / 8 * bp;
        int bitplaneStride = interleaveSize;
        bitmap = ReadBitmap (bytes, offset, rectangle, interleaveSize, scanlineStride, bitplaneStride, bp, palette, paletteOffset, mask);
      }
      else if (mode == ModesEnum.Scanline) {
        int interleaveSize = width / 8;
        int scanlineStride = width / 8 * bp;
        int bitplaneStride = interleaveSize;
        bitmap = ReadBitmap (bytes, offset, rectangle, interleaveSize, scanlineStride, bitplaneStride, bp, palette, paletteOffset, mask);
      }
      else if (mode == ModesEnum.Planar) {
        int interleaveSize = width / 8;
        int scanlineStride = width / 8;
        int bitplaneStride = width * height / 8;
        bitmap = ReadBitmap (bytes, offset, rectangle, interleaveSize, scanlineStride, bitplaneStride, bp, palette, paletteOffset, mask);
      }
      else {
        throw new Exception ();
      }
      return (bitmap);
    }

    public static int CalcSizeInBytes (int width, int height, int nbBitplanes, ModesEnum mode) {
      if (mode == ModesEnum.Chunky) {
        return (width * height / 2); // 16 bytes for 32 pixels
      }
      else {
        return (width * height / 8 * nbBitplanes); // 1 byte for 8 pixels, X bitplanes
      }
    }

    private static Bitmap AllocBitmap (int width, int height, out uint [] buffer) {
      // 4 bytes per pixel, format: ARGB
      buffer = new uint [width * height];
      // if not pinned the GC can move around the array
      GCHandle handle = GCHandle.Alloc (buffer, GCHandleType.Pinned);
      IntPtr pointer = Marshal.UnsafeAddrOfPinnedArrayElement (buffer, 0);
      Bitmap bitmap = new Bitmap (width, height, width * 4, PixelFormat.Format32bppPArgb, pointer);
      return (bitmap);
    }

    private static void WriteColor (uint [] buffer, int dst, uint color, bool isZero, MasksEnum mask) {
      if ((!isZero) || (mask == MasksEnum.Opaque)) {
        buffer [dst] = color;
      }
      else {
        if (mask == MasksEnum.Transparent) {
          buffer [dst] = _transparentARGB;
        }
        else if (mask == MasksEnum.Keyed) {
          buffer [dst] = _keyedARGB;
        }
      }
    }

    private static Bitmap ReadChunky_4bp (byte [] bytes, int src, int width, int height, Palette palette, int paletteOffset, MasksEnum mask) {
      uint [] buffer;
      Bitmap bitmap = AllocBitmap (width, height, out buffer);

      int dst = 0;
      for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x += 2) {
          byte b = (byte) bytes.ReadByte (src++);
          byte color1 = (byte) ((b >> 4) & 0x0F);
          byte color2 = (byte) ((b >> 0) & 0x0F);
          WriteColor (buffer, dst, palette.GetRawARGB (color1 + paletteOffset), color1 == _keyIndexColor, mask);
          dst++;
          WriteColor (buffer, dst, palette.GetRawARGB (color2 + paletteOffset), color2 == _keyIndexColor, mask);
          dst++;
        }
      }
      return (bitmap);
    }

    private static byte GetPixelColor (byte [] bytes, int address, int x, int y, int interleaveSize, int scanlineStride, int bitplaneStride, int nbBitplanes) {
      byte color = 0;
      int byteX = x >> 3; // byteX = X / 8
      for (int bitplane = 0; bitplane < nbBitplanes; bitplane++) {
        // offset of the byte in the interleave
        int offset = (byteX % interleaveSize);
        // offset of the interleave
        offset += (byteX / interleaveSize) * (nbBitplanes * interleaveSize);
        // offset of the bitplane
        offset += bitplane * bitplaneStride;
        // offset of the scanline
        offset += y * scanlineStride;
        if ((bytes.ReadByte (address + offset) & (1 << (7 - (x % 8)))) != 0) {
          color |= (byte) (1 << bitplane);
        }
      }
      return (color);
    }

    //private static Bitmap ReadBitmap (byte [] bytes, int address, int width, int height, int interleaveSize, int scanlineStride, int bitplaneStride, int nbBitplanes, Palette palette, int paletteOffset, MasksEnum mask) {
    //  return (ReadBitmap (bytes, address, 0, 0, width, height, interleaveSize, scanlineStride, bitplaneStride, nbBitplanes, palette, paletteOffset, mask));
    //}

    private static Bitmap ReadBitmap (byte [] bytes, int address, Rectangle rectangle, int interleaveSize, int scanlineStride, int bitplaneStride, int nbBitplanes, Palette palette, int paletteOffset, MasksEnum mask) {
      uint [] buffer;
      Bitmap bitmap = AllocBitmap (rectangle.Width, rectangle.Height, out buffer);

      int dst = 0;
      for (int dy = 0; dy < rectangle.Height; dy++) {
        for (int dx = 0; dx < rectangle.Width; dx++) {
          byte color = GetPixelColor (bytes, address, rectangle.X + dx, rectangle.Y + dy, interleaveSize, scanlineStride, bitplaneStride, nbBitplanes);
          WriteColor (buffer, dst, palette.GetRawARGB (color + paletteOffset), color == _keyIndexColor, mask);
          dst++;
        }
      }
      return (bitmap);
    }

    public Bitmap Bitmap {
      get {
        //if (_bitmap == null) {
        //  CreateSummaryBitmap (_cols);
        //}
        return (_bitmap);
      }
    }

    public int Width {
      get {
        return (_width);
      }
    }

    public int Height {
      get {
        return (_height);
      }
    }

    public void Dispose () {
      //foreach (Bitmap bitmap in _bitmaps) {
      //  bitmap.Dispose ();
      //}
      _bitmap.Dispose ();
    }
  }
}
